package main

import (
	"catfish/purifier"
	"fmt"
	"net"
	_ "os"
	"strconv"
	"strings"
	"time"
)

const (
	EcareReconnectServerInterval = 60  //second
	EcareWriteTimeout            = 240 //second

	LogPrefix = "[Ecare]"
)

var (

	// Ecare Device Request
	EcareReqNew = "GET /newpurifier HTTP/1.1\r\n\r\n"

	// Ecare Device Relay Response
	EcareRspInfo   = `{"Id": "%s","Version":"0.1","Model":"GoClient","Type":"purifier","UserInfo":"%s","Commands":"info status anion ozone fans","TestSeq":%d}`
	EcareRspStatus = `{"anion":%d,"ozone":%d,"fspeed":%d,"tempeture":%s,"rh":%d,"pm25":%d,"air":%d,"TestSeq":%d}`
	EcareRspOk     = `{"rsp":"ok","TestSeq":%d}`
	EcareRspNg     = `{"rsp":"ng","TestSeq":%d}`
)

type EcareDevice struct {
	Id       int
	UserInfo string
	Fan      int
	Ozone    int
	Anion    int
	Tmp      float64
	RH       int
	Pm25     int
	Air      int

	//
	Seq      uint64
	MsgCache *AssertCache

	// internal flow controll
	CatfishServer string
	DeviceConn    *purifier.DeviceConn
	conn          net.Conn
	Quit          bool
	IsClosed      bool
}

func (this *EcareDevice) Status() string {
	return fmt.Sprintf(EcareRspStatus, this.Anion, this.Ozone, this.Fan, strconv.FormatFloat(this.Tmp, 'g', 5, 32), this.RH, this.Pm25, this.Air, this.Seq)
}

func (this *EcareDevice) Info() string {
	return fmt.Sprintf(EcareRspInfo, this.Whoami(), this.UserInfo, this.Seq)
}

func (this *EcareDevice) Ok() string {
	return fmt.Sprintf(EcareRspOk, this.Seq)
}

func (this *EcareDevice) Ng() string {
	return fmt.Sprintf(EcareRspNg, this.Seq)
}

func (this *EcareDevice) Whoami() string {
	idx := strings.Index(this.UserInfo, "@")
	user := this.UserInfo[0:idx]
	return fmt.Sprintf("Go_%s_%d", user, this.Id)
}

func (this *EcareDevice) HandleComonRequest(serverReq string) string {
	if strings.Contains(serverReq, "/quit") {
		return this.Ok()
	} else if strings.Contains(serverReq, "/heartbeat") {
		return this.Ok()
	} else {
		return this.Ng()
	}
}

func (this *EcareDevice) HandleRelayRequest(serverReq string) string {
	this.Seq++
	if strings.Contains(serverReq, "/info") {
		return this.Info()
	} else if strings.Contains(serverReq, "/status") {
		return this.Status()
	} else if strings.Contains(serverReq, "/anion") {
		if strings.Contains(serverReq, "start") {
			this.Anion = 1
			if this.Fan == 0 {
				this.Fan = 3
			}
		} else {
			this.Anion = 0
			if this.Ozone == 0 {
				this.Fan = 0
			}
		}
		return this.Ok()
	} else if strings.Contains(serverReq, "/ozone") {
		if strings.Contains(serverReq, "start") {
			this.Ozone = 1
			if this.Fan == 0 {
				this.Fan = 3
			}
		} else {
			this.Ozone = 0
			if this.Anion == 0 {
				this.Fan = 0
			}
		}
		return this.Ok()

	} else if strings.Contains(serverReq, "/fans") {
		if strings.Contains(serverReq, "speed=3") {
			this.Fan = 3
		} else if strings.Contains(serverReq, "speed=2") {
			this.Fan = 2
		} else if strings.Contains(serverReq, "speed=1") {
			this.Fan = 1
		} else {
			this.Fan = 0
		}
		return this.Ok()
	} else {
		EcareLoger.Debug("%s %s relay request unhandled>>%s", LogPrefix, this.Whoami(), serverReq)
		return this.Ng()
	}
}

func (this *EcareDevice) ConnectAndSayHi() bool {
	var err error
	this.conn, err = net.Dial("tcp", this.CatfishServer)
	if err != nil {
		EcareLoger.Debug("%s %s dial error:%s", LogPrefix, this.Whoami(), err.Error())
		return false
	}
	_, err = this.conn.Write([]byte(EcareReqNew))
	if err != nil {
		EcareLoger.Debug("%s %s write error:%s", LogPrefix, this.Whoami(), err.Error())
		this.conn.Close()
		return false
	}
	this.DeviceConn = purifier.NewDeviceSockConn(nil, this.conn)
	data := []byte(this.Info())
	packet := purifier.NewReqPacket(purifier.PAYLOAD_TYPE_D2S_REQ_NEW_CONNECTION, data)
	this.DeviceConn.Send(packet)

	EcareLoger.Info("%s %s [Send_] %s", LogPrefix, this.Whoami(), packet.ToString())
	return true
}

func (this *EcareDevice) SetMsgCache(cache *AssertCache) {
	this.MsgCache = cache
}

func (this *EcareDevice) Serve() {
	this.IsClosed = true
	this.Quit = false

	for {
		// Connect to Aliyun Server
		if this.ConnectAndSayHi() {
			this.IsClosed = false

			// Run This Ecare Device to handle Request, and generate Response.
			for {
				packet, err := this.DeviceConn.Recieve()
				if err != nil {
					break
				}
				serverReq := packet.ToString()
				respToServer := ""

				pesudoBrowserRequest := false

				if packet.IsRequestPacket() {
					if packet.IsS2dRelayRequest() {
						// verify msg
						if this.MsgCache != nil {
							rIdx := strings.LastIndex(serverReq, "seq=")
							if rIdx != -1 {
								pesudoBrowserRequest = true
								sSeq := serverReq[rIdx+4:]
								this.MsgCache.ReceiveMsg(fmt.Sprintf("b-seq-%s", sSeq), serverReq)
							}
						}
						EcareLoger.Info("%s %s [Recv_] [RelayReq] %s", LogPrefix, this.Whoami(), serverReq)
						respToServer = this.HandleRelayRequest(serverReq)
					} else {
						EcareLoger.Info("%s %s [Recv_] [ComonReq] %s", LogPrefix, this.Whoami(), serverReq)
						respToServer = this.HandleComonRequest(serverReq)
					}
				} else {
					// must be goodbyte, because we have send no request to Server.
					// the only request we send, is "/quit"
					EcareLoger.Info("%s %s [Recv_] [Response] %s", LogPrefix, this.Whoami(), serverReq)
					break
				}

				if this.MsgCache != nil && pesudoBrowserRequest && packet.IsS2dRelayRequest() {
					// only handle http relay request
					this.MsgCache.SendMsg(fmt.Sprintf("e-seq-%d", this.Seq), respToServer)
				}

				replyPkt := purifier.NewReqPacket(purifier.PAYLOAD_TYPE_D2S_RSP_RELAY, []byte(respToServer))
				this.DeviceConn.SetWriteDeadline(time.Now().Add(EcareWriteTimeout * time.Second))
				sendErr := this.DeviceConn.ReplyPacket(replyPkt, packet)
				if sendErr != nil {
					EcareLoger.Info("%s %s [Reply] [Err] %s %s ", LogPrefix, this.Whoami(), sendErr.Error(), replyPkt.ToString())
					if this.MsgCache != nil {
						this.MsgCache.DeleteMsg(fmt.Sprintf("e-seq-%d", this.Seq))
					}
				} else {
					EcareLoger.Info("%s %s [Reply] %s", LogPrefix, this.Whoami(), replyPkt.ToString())
				}
			}

			this.conn.Close()
			this.IsClosed = true
		} else {
			EcareLoger.Warn("%s %d Fail to sayhi! retry in %d(s)\n", LogPrefix, this.Id, EcareReconnectServerInterval)
			// retry after 60 second
			<-time.After(EcareReconnectServerInterval * time.Second)
		}
		if this.Quit {
			goto done
		}
	}
done:
	EcareLoger.Debug("%s %s [Quit]", LogPrefix, this.Whoami())

	if !this.IsClosed {
		this.conn.Close()
	}
}

func (this *EcareDevice) Exit() {
	this.Quit = true
}
